% emacs: -*- mode: latex; coding: utf-8; -*- 

% Copyright (c) 2007, ChenZaichun
% LISENCE: creative common version 3
%

\documentclass[a4paper,11pt]{article}
\usepackage{CJKutf8}
\author{ChenZaichun}
\usepackage[utf8]{inputenc}   % replace by the encoding you are using
%\usepackage[T1]{fontenc}
\title{DShow中实现抓图的几种方法}
\parskip=1.618ex
%\parindent=0pt
\usepackage{graphicx}
\usepackage{listings}
\usepackage{xcolor}
\usepackage[colorlinks,linkcolor=blue,
		hyperindex,
                pdfstartview=FitH,
                plainpages=false,
                unicode,
                pdftex]
                {hyperref}
%\usepackage[sf]{titlesec}
%\usepackage{titletoc}

\usepackage{makeidx,shortvrb,latexsym}

\usepackage{tex2page}
\usepackage{CJKnumb, indentfirst}

\renewcommand{\arraystretch}{1.2}
\usepackage{tabularx}
\usepackage{booktabs,longtable}

\texonly
\lstset{language=C++,%
  tabsize=4,%
  keepspaces=true,%
  breakindent=22pt,% 
  numbers=left,%
  stepnumber=1,%
  numberstyle=\tiny,%
  basicstyle=\footnotesize,%
  showspaces=false,%
  frame=shadowbox,%
  rulesepcolor=\color{red!20!green!20!blue!20},%
  keywordstyle=\color{blue!70},%
  commentstyle=\color{red!50!green!50!blue!50},%
  flexiblecolumns=true,%
  breaklines=true,%
  breakautoindent=true,%
  breakindent=4em,%
  escapeinside={(*@}{@*)},%
  xleftmargin=2em,%
  xrightmargin=2em,% 
  aboveskip=1em,%
  frameround=tttt,%
  nolol=true,%
  showstringspaces=false,%
  escapebegin=\begin{CJK*}{UTF8}{simhei},%
    escapeend=\end{CJK*},%
  texcl=true% 这句很重要，可将注释环境修改成中文环境！
} 
\endtexonly

% \newcommand{itemss} {
%   \setlength{\itemsep}{0ex}
%   \setlength{\leftmargin}{0cm}
%   \setlength{\rightmargin{0cm}
% }

% \lstnewenvironment{cpp}{%
%   \lstset{language=c++,showspaces=true,breaklines=true,%
%   basicstyle=\ttfamily,%
%   escapebegin=\begin{CJK*}{UTF8}{simhei},escapeend=\end{CJK*},texcl=true% 这句很重要，可将注释环境修改成中文环境！
%   }}{}

\begin{document}
\texonly
\pdfmapfile{=simhei.map}
\begin{CJK}{UTF8}{simhei}
\endtexonly
%\CJKtilde
\CJKindent

\maketitle
\begin{center}
$Id$
\end{center}

\tableofcontents

\section{加入Sample Grabber Filter}
当我们加入Sample Grabber Filter的时候，我们可以直接调用其接口（interface）ISampleGrabber。该接口可以获取经过该Filter的单独的Media Samples。详情请参见DXSDK。

\subsection{派生出自己的Sample Grabber}

从ISampleGrabberCB中派生出自己的类，然后实现其虚函数，详情请参见SDK中的示例程序\verb|(DXSDK ROOT\Samples\C++\DirectShow\Editing\GrabBitmaps)|。

\subsection{直接调用Sample Grabber Filter的接口}

如果我们在播放的过程中动态的加入Filter的话，操作和效率都不乐观。所以我采用下面的方法：

该方法传递的是时间，不是在播放的时候动态加入Filter然后截图，而是另外打开源文件进行操作。

\begin{enumerate}
\item {申明以下接口：
    \begin{lstlisting}[language=c++, caption=``声明接口'']
IGraphBuilder 	*pGraph 	= NULL; //for graph builder
IMediaControl 	*pControl 	= NULL; //media control
IMediaSeeking 	*pSeeking 	= NULL; //media seeking
IMediaEventEx 	*pEvent 	= NULL; //media envent
IBaseFilter    	*pNullFilter 	= NULL; //for holding the Sample grabber Filter
    \end{lstlisting}}
\item{初始化接口：
  \begin{lstlisting}[language=c++,caption=``初始化接口'']
JIF(CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC, IID_IGraphBuilder, (void **)&pGraph));
JIF(CoCreateInstance(CLSID_NullRenderer, NULL, CLSCTX_INPROC, IID_IBaseFilter, (void **)&pNullFilter));
JIF(pGraph->QueryInterface(IID_IMediaControl,(void **)&pControl));
JIF(pGraph->QueryInterface(IID_IMediaSeeking, (void **)&pSeeking));
JIF(pGraph->QueryInterface(IID_IMediaEvent, (void **)&pEvent));
  \end{lstlisting}}
\item{创建Sample Grabber：
    \begin{lstlisting}[language=c++,caption=``create sample grabber'']
// Create the Sample Grabber.
IBaseFilter *pGrabberF = NULL;
JIF(CoCreateInstance(CLSID_SampleGrabber,NULL, CLSCTX_INPROC_SERVER, IID_IBaseFilter, (void**)&pGrabberF));
JIF(pGraph->AddFilter(pGrabberF, L"Sample Grabber"));
JIF(pGraph->AddFilter(pNullFilter, L"Null Render Filter"));
ISampleGrabber *pGrabber;
JIF(pGrabberF->QueryInterface(IID_ISampleGrabber, (void**)&pGrabber));
    \end{lstlisting}}
%\item{设置Sample Grabber的媒体格式：
%调用SetMediaType，该函数接受一个AM_MEDIA_TYPE的结构，主要是设置该结构中的majortype，和subtype域。}
\item{添加Source Filter：
    \begin{lstlisting}[language=c++,caption=``add source filter'']
IBaseFilter *pSrc;
JIF(pGraph->AddSourceFilter(T2W(m_szFile), L"Source", &pSrc));
    \end{lstlisting}}
  \item{连接Grabber 和 NullRender两个Filter：
    \begin{lstlisting}
IPin  *pOutPin;
hr = GetPin(pGrabberF, PINDIR_OUTPUT, &pOutPin);
 
IPin  *pInPin;
hr = GetPin(pNullFilter, PINDIR_INPUT, &pInPin);
 
pGraph->Connect(pOutPin, pInPin);
    \end{lstlisting}}
  \item{取得当前所连接媒体的类型
    \begin{lstlisting}[language=c++,caption=``get media type'']
AM_MEDIA_TYPE mt;
hr = pGrabber->GetConnectedMediaType(&mt);
// Examine the format block.
VIDEOINFOHEADER *pVih;
if ((mt.formattype == FORMAT_VideoInfo) && 
	(mt.cbFormat >= sizeof(VIDEOINFOHEADER)) &&
	(mt.pbFormat != NULL) ) {
	pVih = (VIDEOINFOHEADER*)mt.pbFormat;
} else {
	// Wrong format. Free the format block and return an error.
	FreeMediaType(mt);
	return VFW_E_INVALIDMEDIATYPE; 
}
 
// Do buffer the samples as they pass through
//
hr = pGrabber->SetBufferSamples(TRUE);

// Only grab one at a time, stop stream after
// grabbing one sample
//
hr = pGrabber->SetOneShot( TRUE );
    \end{lstlisting}}
  \item{Seeking文件，使其到达要截图的时间帧
    \begin{lstlisting}[language=c++, caption=``get timeline'']
pSeeking->SetPositions(pCurrentPos, AM_SEEKING_AbsolutePositioning, NULL, AM_SEEKING_NoPositioning);
 
pControl->Run();

long EvCode = 0;
 
hr = pEvent->WaitForCompletion( INFINITE, &EvCode );
    \end{lstlisting}}
  \item{取得当前的buffer数据
    \begin{lstlisting}[language=c++,caption=``get data'']
// Find the required buffer size.
long cbBuffer = 0;
hr = pGrabber->GetCurrentBuffer(&cbBuffer, NULL);
LONGLONG currentPos;
pSeeking->GetCurrentPosition(&currentPos);
BYTE *pBuffer = new BYTE[cbBuffer];
if (!pBuffer) {
	// Out of memory. Return an error code.
	Msg("Out of Memory");
}
hr = pGrabber->GetCurrentBuffer(&cbBuffer, (long*)pBuffer);
    \end{lstlisting}}
  \item{写入文件
    \begin{lstlisting}[language=c++,caption=``write file'']
// Create a file to hold the bitmap
HANDLE hf = CreateFile(szFilename, GENERIC_WRITE, FILE_SHARE_READ, NULL, CREATE_ALWAYS, NULL, NULL );
if( hf == INVALID_HANDLE_VALUE ) {
	// Failed to create file
	return 0;
}
 	
// Write out the file header
//
BITMAPFILEHEADER bfh;
memset( &bfh, 0, sizeof( bfh ) );
bfh.bfType = 'MB';
bfh.bfSize = sizeof( bfh ) + cbBuffer + sizeof( BITMAPINFOHEADER );
bfh.bfOffBits = sizeof(BITMAPINFOHEADER)+sizeof( BITMAPFILEHEADER );
 	
DWORD Written = 0;
WriteFile( hf, &bfh, sizeof( bfh ), &Written, NULL );
 	
// Write the bitmap format
//
BITMAPINFOHEADER bih;
memset( &bih, 0, sizeof( bih ) );
bih.biSize = sizeof( bih );
bih.biWidth = pVih->bmiHeader.biWidth;
bih.biHeight = pVih->bmiHeader.biHeight;
bih.biPlanes = pVih->bmiHeader.biPlanes;
bih.biBitCount = pVih->bmiHeader.biBitCount;
 
Written = 0;
WriteFile( hf, &bih, sizeof( bih ), &Written, NULL );
 
// Write the bitmap bits
//
Written = 0;
WriteFile( hf, pBuffer, cbBuffer, &Written, NULL );
FreeMediaType(mt);
CloseHandle(hf);
    \end{lstlisting}}
  \item{释放资源
    \begin{lstlisting}
pControl->Stop();	
SAFE_RELEASE(pControl);
SAFE_RELEASE(pSeeking);
SAFE_RELEASE(pEvent);
SAFE_RELEASE(pSrc);
SAFE_RELEASE(pNullFilter);
SAFE_RELEASE(pGrabber);
SAFE_RELEASE(pGrabberF);
SAFE_RELEASE(pGraph);
    \end{lstlisting}}
  \item{其实我们可以不用NullRender，而是用IVideoWindow接口来实现。如果是那样的话，首先申明IVideoWindow *pVideo = NULL;将pVideo加入到Filter Graph中
    \begin{lstlisting}
JIF(pGraph->QueryInterface(IID_IVideoWindow,(void**)&pVideo));
hr = pGraph->Render(pOutPin);
if (pVideo) {
        hr = pVideo->put_AutoShow(OAFALSE);
}
    \end{lstlisting}}
\end{enumerate}

\section{通过IBasicVideo::GetCurrentImage接口}

对于该接口，Video Renderer和Video Mixing Renderer（VMR）有不同的实现。

\begin{itemize}
\item{Video Renderer:\\
如果该Renderer使用了DDraw加速的话，该调用会失败。在调用该接口的时候，必须首先暂停Renderer（可以通过IMediaControl::Pause暂停，如果不能确信该操作是否成功，应该调用IMediaControl::GetState判断状态）。}
\item{Video Mixing Renderer:\\
对于VMR，该方法都会成功（不管是否运用了DDraw加速，也不管是否是暂停状态），此时对于它所有的状态（running, stopped, or paused）都适用。\\
函数Grabber代码如下（调用该函数的时候应该先将媒体文件暂停，原因上面已经说了）：
\
\begin{lstlisting}
bool Grabber(IBasicVideo mBasicVideo, TCHAR *szFilename)
{
	if (mBasicVideo)
 	{
		long bitmapSize = 0;
	 	if(SUCCEEDED(mBasicVideo->GetCurrentImage(&bitmapSize, 0))) {
 			//if语句里面的操作时取得buffer的size。
 			//当我们在布确定image buffer的大小的情况下，我们给
	 		//GetCurrentImage的第二个参数传递0或者NULL，取得buffer的
 			//大小供以后使用。
 			bool pass = false;
 			unsigned char * buffer = new unsigned char[bitmapSize];
			if(SUCCEEDED(mBasicVideo->GetCurrentImage(&bitmapSize,(long*)buffer))) {
				//此时已经用到刚才所取得的大小（分配空间）
 				BITMAPFILEHEADER	hdr;    //Bitmap的头信息
 				LPBITMAPINFOHEADER	lpbi; // Bitmap的文件信息（包括数据）
 
 				lpbi = (LPBITMAPINFOHEADER)buffer;
 
 				int nColors = 1 << lpbi->biBitCount;
 				if (nColors > 256)
 					nColors = 0;
 
 				hdr.bfType		= ((WORD) ('M' << 8) | 'B');	//always is "BM"
 				hdr.bfSize		= bitmapSize + sizeof( hdr );
 				hdr.bfReserved1 	= 0;
 				hdr.bfReserved2 	= 0;
 				hdr.bfOffBits		= (DWORD) (sizeof(BITMAPFILEHEADER) + lpbi->biSize 
 				CFile bitmapFile(outFile, CFile::modeReadWrite | CFile::modeCreate | CFile::typeBinary);
 				bitmapFile.Write(&hdr, sizeof(BITMAPFILEHEADER));
 				bitmapFile.Write(buffer, bitmapSize);
 				bitmapFile.Close();
 				pass = true;
 			}
 			delete [] buffer; //数据用过之后记得要释放空间
 			return true;
 		}
 	}
 	
 	return false;
}
\end{lstlisting}}
\end{itemize}

\section{IMediaDet接口}

IMediaDet接口可以取得媒体文件的信息。SDK里面的示例代码（没有写入文件）：

\begin{lstlisting}
long size;
//取得图像帧的大小，给GetBitmapBits的第三个参数传递0 or NULL
hr = pDet->GetBitmapBits(0, &size, 0, width, height);
if(SUCCEEDED(hr)) {
 	char *pBuffer = new char[size];
 	if(!pBuffer) {
 		return E_OUTOFMEMORY;
 	}
 	
 	try {
 		hr = pDet->GetbitmapsBits(0, 0, pBuffer, width, height);
 	} catch(...) {
 		delete [] pBuffer;
 		throw;
 	}
	
 	if(SUCCEEDED(hr)) {
 		BITMAPINFOHEADER *bmih = (BITMAPINFOHEADER*)pBuffer;
 		HDC		hdcDest = GetDC(0);
 		
 		//Find the address of the start of the image data
 		void *pData = pBuffer + sizeof(BITMAPINFOHEADER);
 		
 		//Note: In general a BITMAPINFOHEADER can include extra color
 		//information at the end, so calculating the offset to the image
		//data i snot generally correct. However, the IMediaDet interface
 		//always returns an RGB-24 image with no extra color information
 			
 		BITMAPINFO bmi;
		ZeroMemory(&bmi, sizeof(BITMAPINFO));
 		CopyMemory(&(bmi.bmiHeader), bmih, sizeof(BITMAPINFOHEADER));
 		HBITMAP hBitmap = CreateDIBitmap(hdcDect, bmih, CBM_INIT,
						pData, &bmi, DIB_RGB_COLORS);
 	}
 	
 	delete [] pBuffer;
}
\end{lstlisting}
该方法并没有写入bitmap，具体的写入过程可以参加上面的几种方法。

\texonly
\end{CJK}
\endtexonly
\end{document}

